﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Xml;
using System.Xml.XPath;
using System.Drawing;
using Emgu.CV;
using Emgu.CV.Structure;
using Emgu.CV.Features2D;
using System.Diagnostics;
using Emgu.CV.CvEnum;

namespace MAVI.TagAndRecognizeObjects.StickerDetection.source
{
    public class RecognizeSticker
    {
        private String XMLPath = "D:/ImagineCup/Stickers/stickers.xml";
        private List<Sticker> stickers;
        private String finalStickerName = "not recognized";

        public String FinalStickerName
        {
            get { return this.finalStickerName; }
        }

        public RecognizeSticker()
        {
            this.stickers = new List<Sticker>();
            LoadFromXML();
        }

        private void LoadFromXML()
        {
            try
            {
                XmlDocument doc = new XmlDocument();
                doc.Load(XMLPath);
                XPathNavigator navigator = doc.CreateNavigator();
                navigator.MoveToRoot();

                if (navigator.MoveToFirstChild())
                {
                    if (navigator.MoveToFirstChild())
                    {
                        do
                        {
                            String stickerName = String.Empty;
                            String stickerPath = String.Empty;
                            if (navigator.MoveToChild("stickerName", String.Empty))
                            {
                                stickerName = navigator.Value;
                                navigator.MoveToParent();
                            }

                            if (navigator.MoveToChild("stickerPath", String.Empty))
                            {
                                stickerPath = navigator.Value;
                                navigator.MoveToParent();
                            }

                            if (!stickerName.Equals(String.Empty) && !stickerPath.Equals(String.Empty))
                            {
                                Sticker sticker = new Sticker(stickerName, stickerPath, new Bitmap(stickerPath));
                                stickers.Add(sticker);
                            }
                        }

                        while (navigator.MoveToNext());
                    }
                }
            }
            catch (Exception e)
            {

            }
        }

        public String Recognize(Bitmap imageCaptured)
        {
            #region Extract Sticker from Captured Image

            Image<Bgr, Byte> img = new Image<Bgr, byte>(imageCaptured);


            //Convert the image to grayscale and filter out the noise
            Image<Gray, Byte> gray = img.Convert<Gray, Byte>().PyrDown().PyrUp();

            //Convert to B&W image by adaptive thresholding
            #region AdaptiveThresholding

            System.IntPtr grayPtr = gray.Ptr;

            CvInvoke.cvAdaptiveThreshold(grayPtr, grayPtr, 255, Emgu.CV.CvEnum.ADAPTIVE_THRESHOLD_TYPE.CV_ADAPTIVE_THRESH_GAUSSIAN_C,
                Emgu.CV.CvEnum.THRESH.CV_THRESH_BINARY, 19, 5);

            #endregion

            Gray cannyThreshold = new Gray(80);
            Gray cannyThresholdLinking = new Gray(120);

            Image<Gray, Byte> cannyEdges = gray.Canny(cannyThreshold, cannyThresholdLinking);

            //cannyEdges.ToBitmap().Save("D:\\ImagineCup\\Images\\Stickere\\" + "stickerCanny" + ".jpg", ImageFormat.Jpeg);

            //gray.ToBitmap().Save("D:\\ImagineCup\\Images\\Stickere\\" + "stickerThresh" + ".jpg", ImageFormat.Jpeg);

            LineSegment2D[] lines = cannyEdges.HoughLinesBinary(
                1, //Distance resolution in pixel-related units
                Math.PI / 45.0, //Angle resolution measured in radians.
                20, //threshold
                30, //min Line width
                10 //gap between lines
                )[0]; //Get the lines from the first channel

            #region Find triangles and rectangles

            //cannyEdges.ToBitmap().Save("D:\\ImagineCup\\Images\\Stickere\\" + "stickerCanny" + ".jpg", ImageFormat.Jpeg);

            List<Triangle2DF> triangleList = new List<Triangle2DF>();
            List<MCvBox2D> boxList = new List<MCvBox2D>();

            using (MemStorage storage = new MemStorage()) //allocate storage for contour approximation
                for (Contour<Point> contours = cannyEdges.FindContours(); contours != null; contours = contours.HNext)
                {
                    Contour<Point> currentContour = contours.ApproxPoly(contours.Perimeter * 0.05, storage);

                    if (contours.Area > 250) //only consider contours with area greater than 250
                    {
                        if (currentContour.Total == 3) //The contour has 3 vertices, it is a triangle
                        {
                            Point[] pts = currentContour.ToArray();
                            triangleList.Add(new Triangle2DF(
                               pts[0],
                               pts[1],
                               pts[2]
                               ));
                        }
                        else if (currentContour.Total == 4) //The contour has 4 vertices.
                        {
                            #region determine if all the angles in the contour are within the range of [80, 100] degree

                            bool isRectangle = true;
                            Point[] pts = currentContour.ToArray();
                            LineSegment2D[] edges = PointCollection.PolyLine(pts, true);

                            for (int i = 0; i < edges.Length; i++)
                            {
                                double angle = Math.Abs(
                                   edges[(i + 1) % edges.Length].GetExteriorAngleDegree(edges[i]));
                                if (angle < 80 || angle > 100)
                                {
                                    isRectangle = false;
                                    break;
                                }
                            }
                            #endregion



                            if (isRectangle) boxList.Add(currentContour.GetMinAreaRect());
                        }
                    }
                }

            #endregion


            #region FindSticker(Triangle in Rectangle)

            List<Triangle2DF> triangleList2 = new List<Triangle2DF>();
            List<MCvBox2D> boxList2 = new List<MCvBox2D>();

            Triangle2DF tr = new Triangle2DF();

            foreach (MCvBox2D box in boxList)
            {

                PointF[] pointsRect = box.GetVertices();



                foreach (Triangle2DF triangle in triangleList)
                {

                    if (TriangleInRectangle(pointsRect, triangle))
                    {

                        if (triangle.Area > tr.Area)
                        {
                            tr = triangle;
                        }
                        boxList2.Add(box);

                    }
                }

                if (boxList2.Count != 0)
                {
                    triangleList2.Add(tr);
                    break;
                }

            }

            #endregion

            

            #region StickerExtraction

                
                    double rotation = boxList2.ElementAt(0).angle;

                    //extracting the Region Of Interest in the image where the sticker is situated so that the center of the image will be the center of the sticker 
                    gray.ROI = new Rectangle((int)(boxList2.ElementAt(0).center.X - 20 - Math.Sqrt(boxList2.ElementAt(0).size.Height * boxList2.ElementAt(0).size.Height
                        + boxList2.ElementAt(0).size.Width * boxList2.ElementAt(0).size.Width) / 2),
                        (int)(boxList2.ElementAt(0).center.Y - 20 - Math.Sqrt(boxList2.ElementAt(0).size.Height * boxList2.ElementAt(0).size.Height
                        + boxList2.ElementAt(0).size.Width * boxList2.ElementAt(0).size.Width) / 2),
                        (int)Math.Sqrt(boxList2.ElementAt(0).size.Height * boxList2.ElementAt(0).size.Height
                        + boxList2.ElementAt(0).size.Width * boxList2.ElementAt(0).size.Width) + 25,
                        (int)Math.Sqrt(boxList2.ElementAt(0).size.Height * boxList2.ElementAt(0).size.Height
                        + boxList2.ElementAt(0).size.Width * boxList2.ElementAt(0).size.Width) + 25);

                    Image<Gray, Byte> imageToPaste = new Image<Gray, byte>(gray.Size);

                    CvInvoke.cvCopy(gray, imageToPaste, IntPtr.Zero);

                    //imageToPaste.ToBitmap().Save("D:\\ImagineCup\\Images\\Stickere\\" + "stickerFinal2" + ".jpg", ImageFormat.Jpeg);

                    double centerX, centerY;

                    centerX = imageToPaste.Height / 2;
                    centerY = imageToPaste.Width / 2;


                    //rotates the image so that the sticker will be in a default position and we can extract it from the image
                    imageToPaste = imageToPaste.Rotate(-rotation, new Gray(255));

                    imageToPaste.ROI = new Rectangle((int)(centerX - boxList2.ElementAt(0).size.Height / 2 - 20),
                                                     (int)(centerY - boxList2.ElementAt(0).size.Height / 2 - 20),
                                                     (int)(boxList2.ElementAt(0).size.Height + 40),
                                                     (int)(boxList2.ElementAt(0).size.Height + 40));


                    Image<Gray, Byte> finalSticker = new Image<Gray, Byte>(imageToPaste.Size);

                    CvInvoke.cvCopy(imageToPaste, finalSticker, IntPtr.Zero);

                    finalSticker = finalSticker.Resize(200, 200, INTER.CV_INTER_AREA);

                              

                                    
                #endregion
            
            #endregion

            int featuresDetected, max=0;
            
            foreach (Sticker sticker in stickers)
            {
                featuresDetected = SURFDetect(sticker.StickerImage, finalSticker.ToBitmap());
                
                if (featuresDetected > max)
                {
                    max = featuresDetected;
                    finalStickerName = sticker.StickerName;
                }
            }

            return finalStickerName;       

        }

        private bool TriangleInRectangle(PointF[] pointsRect, Triangle2DF triangle)
        {

            if (TriangleArea(pointsRect[0], pointsRect[1], triangle.V0) < 0 || TriangleArea(pointsRect[1], pointsRect[2], triangle.V0) < 0 || TriangleArea(pointsRect[2], pointsRect[3], triangle.V0) < 0 || TriangleArea(pointsRect[3], pointsRect[0], triangle.V0) < 0)
            {
                return false;
            }

            if (TriangleArea(pointsRect[0], pointsRect[1], triangle.V1) < 0 || TriangleArea(pointsRect[1], pointsRect[2], triangle.V1) < 0 || TriangleArea(pointsRect[2], pointsRect[3], triangle.V1) < 0 || TriangleArea(pointsRect[3], pointsRect[0], triangle.V1) < 0)
            {
                return false;
            }

            if (TriangleArea(pointsRect[0], pointsRect[1], triangle.V2) < 0 || TriangleArea(pointsRect[1], pointsRect[2], triangle.V2) < 0 || TriangleArea(pointsRect[2], pointsRect[3], triangle.V1) < 0 || TriangleArea(pointsRect[3], pointsRect[0], triangle.V2) < 0)
            {
                return false;
            }
            return true;
        }

        private double TriangleArea(PointF point1, PointF point2, PointF point3)
        {

            double area = 0.5 * (point2.X * point3.Y - point2.Y * point3.X - point1.X * point3.Y + point1.Y * point3.X + point1.X * point2.Y - point1.Y * point2.X);

            return area;

        }
        
        private static int SURFDetect(Bitmap imageTest, Bitmap imageTaken)
        {
            long time1 = DateTime.Now.Ticks;
            int n = 0;

            int length = 0;

            SURFDetector surfParam = new SURFDetector(600, false);

            Image<Gray, Byte> modelImage = new Image<Gray, byte>(imageTest);
            //extract features from the object image
            ImageFeature[] modelFeatures = surfParam.DetectFeatures(modelImage, null);

            //Create a Feature Tracker
            Features2DTracker tracker = new Features2DTracker(modelFeatures);

            Image<Gray, Byte> observedImage = new Image<Gray, byte>(imageTaken);

            //observedImage._EqualizeHist();

            Stopwatch watch = Stopwatch.StartNew();
            // extract features from the observed image
            ImageFeature[] imageFeatures = surfParam.DetectFeatures(observedImage, null);

            Features2DTracker.MatchedImageFeature[] matchedFeatures = tracker.MatchFeature(imageFeatures, 2, 20);
            matchedFeatures = Features2DTracker.VoteForUniqueness(matchedFeatures, 0.8);
            matchedFeatures = Features2DTracker.VoteForSizeAndOrientation(matchedFeatures, 1.5, 20);
            HomographyMatrix homography = Features2DTracker.GetHomographyMatrixFromMatchedFeatures(matchedFeatures);
            watch.Stop();

            //Merge the object image and the observed image into one image for display
            //Image<Gray, Byte> res = modelImage.ConcateVertical(observedImage);

            /*#region draw lines between the matched features
            foreach (Features2DTracker.MatchedImageFeature matchedFeature in matchedFeatures)
            {
                PointF p = matchedFeature.ObservedFeature.KeyPoint.Point;
                p.Y += modelImage.Height;
                res.Draw(new LineSegment2DF(matchedFeature.SimilarFeatures[0].Feature.KeyPoint.Point, p), new Gray(0), 1);
            }
            #endregion
             */
            length = matchedFeatures.Length;



            #region draw the project region on the image
            if (homography != null)
            {  //draw a rectangle along the projected model
                /* Rectangle rect = modelImage.ROI;
                 PointF[] pts = new PointF[] { 
                new PointF(rect.Left, rect.Bottom),
                new PointF(rect.Right, rect.Bottom),
                new PointF(rect.Right, rect.Top),
                new PointF(rect.Left, rect.Top)};
                 homography.ProjectPoints(pts);

                 for (int i = 0; i < pts.Length; i++)
                     pts[i].Y += modelImage.Height;

                 res.DrawPolyline(Array.ConvertAll<PointF, Point>(pts, Point.Round), true, new Gray(255.0), 5);
                 */

                n = 1;
            }
            #endregion
            long time2 = DateTime.Now.Ticks;
            Console.WriteLine("Timp pentru SURF:" + (time2 - time1));
            //ImageViewer.Show(res, String.Format("Matched in {0} milliseconds", watch.ElapsedMilliseconds));
            return length;

        }
    }
}